﻿using BBICMS.Resources;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace BBICMS.Repository
{
    public class GenericRepository<T> : IRepository<T> where T : class
    {
        protected DbSet<T> _dbSet { get; set; }
        protected DbContext _context { get; set; }

        public GenericRepository(DbContext context)
        {
            if (context == null)
            {
                throw new ArgumentException("An instance of DbContext is " +
                    "required to use this repository.", "context");
            }

            this._context = context;
            this._dbSet = this._context.Set<T>();
        }

        public virtual IEnumerable<T> All()
        {
            return this._dbSet.AsNoTracking();
        }

        public virtual IQueryable<T> GetAll()
        {
            return this._dbSet;
        }
        public virtual IQueryable<T> GetAll(params Expression<Func<T, object>>[] includes)
        {
            var query = this._dbSet;
            if (includes.Any())
            {
                return includes.Aggregate(query.AsQueryable(), (current, include) => current.Include(include));
            }
            return query;
        }
        public IEnumerable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
        {

            IEnumerable<T> query = this._dbSet.Where(predicate).AsEnumerable();
            return query;
        }

        public Task<IEnumerable<T>> FindByAsync<KProperty>(Expression<Func<T, bool>> filter, Expression<Func<T, KProperty>> orderByExpression, bool ascending,
          CancellationToken cancellationToken = default(CancellationToken),
          params Expression<Func<T, object>>[] includes)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (filter == null)
                throw new ArgumentNullException("filter", "过滤条件filter不能为空");

            IQueryable<T> queryable = this._dbSet.Where<T>(filter);

            //   if (includes.Any())
            //       queryable = includes.Aggregate(GetSet().AsQueryable(), (current, include) => current.Include(include));
            // Task.FromResult<IEnumerable<T>>(queryable.Where<T>(filter));


            if (ascending)
            {
                if (includes.Any())
                {
                    queryable = includes.Aggregate(queryable.OrderBy(orderByExpression).AsQueryable(), (current, include) => current.Include(include));
                    return Task.FromResult<IEnumerable<T>>(queryable);
                }
                queryable = queryable.OrderBy(orderByExpression);
                return Task.FromResult<IEnumerable<T>>(queryable);

            }
            else
            {
                if (includes.Any())
                {
                    queryable = includes.Aggregate(queryable.OrderByDescending(orderByExpression).AsQueryable(), (current, include) => current.Include(include));
                    return Task.FromResult<IEnumerable<T>>(queryable);

                }
                queryable = queryable.OrderByDescending(orderByExpression);
                return Task.FromResult<IEnumerable<T>>(queryable);

            }

        }


        /// <summary>
        /// <see cref="SIG.Core.IRepository.GetFilteredElements{T}"/>
        /// </summary>
        /// <param name="filter"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <returns><see cref="SIG.Core.IRepository{T}"/></returns>
        public virtual IEnumerable<T> GetFilteredElements(Expression<Func<T, bool>> filter)
        {
            if (filter == (Expression<Func<T, bool>>)null)
                throw new ArgumentNullException("filter", "过滤条件filter不能为空");

            return this._dbSet.Where(filter)
                           .AsEnumerable();
        }

        /// <summary>
        /// <see cref="SIG.Core.IRepository.GetFilteredElements{T}"/>
        /// </summary>
        /// <param name="filter"><see cref="SIG.Core.IRepository.GetFilteredElements{T}"/></param>
        /// <param name="includes"><see cref="SIG.Core.IRepository.GetFilteredElements{T}"/></param>
        /// <returns><see cref="SIG.Core.IRepository.GetFilteredElements{T}"/></returns>
        public virtual IEnumerable<T> GetFilteredElements(Expression<Func<T, bool>> filter,
            params Expression<Func<T, object>>[] includes)
        {
            if (filter == (Expression<Func<T, bool>>)null)
                throw new ArgumentNullException("filter", "过滤条件filter不能为空");

            if (includes == (Expression<Func<T, object>>[])null)
                throw new ArgumentNullException("includes", "包含条件includes不能为空");

            var query = this._dbSet.Where(filter);
            if (includes.Any())
            {
                return includes.Aggregate(query.AsQueryable(), (current, include) => current.Include(include));
            }
            return query;
        }


        public T GetFirstOrDefault(Expression<Func<T, bool>> filter,
           params Expression<Func<T, object>>[] includes)
        {
            IQueryable<T> queryable = this._dbSet;
            if (includes.Any())
                queryable = includes.Aggregate(this._dbSet.AsQueryable(), (current, include) => current.Include(include));
            return queryable.FirstOrDefault(filter);
        }


        public Task<T> GetFirstOrDefaultAsync(Expression<Func<T, bool>> filter,
            CancellationToken cancellationToken = default(CancellationToken),
            params Expression<Func<T, object>>[] includes)
        {
            IQueryable<T> queryable = this._dbSet;
            if (includes.Any())
                queryable = includes.Aggregate(this._dbSet.AsQueryable(), (current, include) => current.Include(include));
            return filter == null ? queryable.FirstOrDefaultAsync(cancellationToken) : queryable.FirstOrDefaultAsync(filter, cancellationToken);
        }

        public virtual T GetById(object id)
        {
            return this._dbSet.Find(id);
        }

        public virtual void Add(T entity)
        {
            DbEntityEntry entry = this._context.Entry(entity);
            if (entry.State != EntityState.Detached)
            {
                entry.State = EntityState.Added;
            }
            else
            {
                this._dbSet.Add(entity);
            }
        }

        public virtual void Update(T entity)
        {
            DbEntityEntry entry = this._context.Entry(entity);

            if (entry.State == EntityState.Detached)
            {
                this._dbSet.Attach(entity);
            }

            entry.State = EntityState.Modified;
        }

        public virtual void Detach(T entity)
        {
            DbEntityEntry entry = this._context.Entry(entity);

            entry.State = EntityState.Detached;
        }

        public virtual void Delete(T entity)
        {
            DbEntityEntry entry = this._context.Entry(entity);

            if (entry.State != EntityState.Deleted)
            {
                entry.State = EntityState.Deleted;
            }
            else
            {
                this._dbSet.Attach(entity);
                this._dbSet.Remove(entity);
            }
        }

        public virtual void Delete(int id)
        {
            var entity = this.GetById(id);

            if (entity != null)
            {
                this.Delete(entity);
            }
        }


        /// <summary>
        /// <see cref="SIG.Core.IRepository{T}"/>
        /// </summary>
        /// <typeparam name="S"><see cref="SIG.Core.IRepository{T}"/></typeparam>
        /// <param name="pageIndex"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <param name="pageSize"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <param name="orderByExpression"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <param name="ascending"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <returns><see cref="SIG.Core.IRepository{T}"/></returns>
        public virtual IEnumerable<T> GetPagedElements<KProperty>(int pageIndex, int pageSize,
            Expression<Func<T, KProperty>> orderByExpression, bool ascending)
        {
            if (pageIndex < 0)
                throw new ArgumentException(Messages.InvalidPageIndexException, "pageIndex");

            if (pageSize <= 0)
                throw new ArgumentException(Messages.InvalidPageCountException, "pageSize");

            if (orderByExpression == (Expression<Func<T, KProperty>>)null)
                throw new ArgumentNullException("orderByExpression", Messages.OrderByExpressionCannotBeNullException);

            var set = this._dbSet;

            if (ascending)
            {
                return set.OrderBy(orderByExpression)
                          .Skip(pageSize * pageIndex)
                          .Take(pageSize)
                          .AsEnumerable();
            }
            else
            {
                return set.OrderByDescending(orderByExpression)
                          .Skip(pageSize * pageIndex)
                          .Take(pageSize)
                          .AsEnumerable();
            }
        }

        /// <summary>
        /// <see cref="SIG.Core.IRepository{T}"/>
        /// </summary>
        /// <typeparam name="S"><see cref="SIG.Core.IRepository{T}"/></typeparam>
        /// <param name="pageIndex"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <param name="pageSize"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <param name="orderByExpression"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <param name="ascending"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <returns><see cref="SIG.Core.IRepository{T}"/></returns>
        public virtual IEnumerable<T> GetPagedElements<KProperty>(int pageIndex, int pageSize,
            Expression<Func<T, KProperty>> orderByExpression, bool ascending,
            params Expression<Func<T, object>>[] includes)
        {
            if (pageIndex < 0)
                throw new ArgumentException(Messages.InvalidPageIndexException, "pageIndex");

            if (pageSize <= 0)
                throw new ArgumentException(Messages.InvalidPageCountException, "pageSize");

            if (orderByExpression == (Expression<Func<T, KProperty>>)null)
                throw new ArgumentNullException("orderByExpression", Messages.OrderByExpressionCannotBeNullException);

            if (includes == (Expression<Func<T, object>>[])null)
                throw new ArgumentNullException("includes", Messages.IncludesCannotBeNullException);

            var set = this._dbSet;

            if (ascending)
            {
                if (includes.Any())
                {
                    return includes.Aggregate(set.OrderBy(orderByExpression).Skip(pageSize * pageIndex).Take(pageSize).AsQueryable(),
                                     (current, include) => current.Include(include));
                }

                return set.OrderBy(orderByExpression).Skip(pageSize * pageIndex).Take(pageSize);
            }
            else
            {
                if (includes.Any())
                {
                    return includes.Aggregate(set.OrderByDescending(orderByExpression).Skip(pageSize * pageIndex).Take(pageSize).AsQueryable(),
                                            (current, include) => current.Include(include));
                }

                return set.OrderByDescending(orderByExpression).Skip(pageSize * pageIndex).Take(pageSize);
            }
        }

        /// <summary>
        /// <see cref="SIG.Core.IRepository{T}"/>
        /// </summary>
        /// <typeparam name="S"><see cref="SIG.Core.IRepository{T}"/></typeparam>
        /// <param name="pageIndex"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <param name="pageSize"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <param name="orderByExpression"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <param name="ascending"><see cref="SIG.Core.IRepository{T}"/></param>
        /// <returns><see cref="SIG.Core.IRepository{T}"/></returns>
        public virtual IEnumerable<T> GetPagedElements<KProperty>(int pageIndex, int pageSize,
            Expression<Func<T, KProperty>> orderByExpression, Expression<Func<T, bool>> filter, bool ascending,
            params Expression<Func<T, object>>[] includes)
        {
            if (pageIndex < 0)
                throw new ArgumentException(Messages.InvalidPageIndexException, "pageIndex");

            if (pageSize <= 0)
                throw new ArgumentException(Messages.InvalidPageCountException, "pageSize");

            if (orderByExpression == (Expression<Func<T, KProperty>>)null)
                throw new ArgumentNullException("orderByExpression", Messages.OrderByExpressionCannotBeNullException);

            if (includes == (Expression<Func<T, object>>[])null)
                throw new ArgumentNullException("includes", Messages.IncludesCannotBeNullException);

            if (filter == (Expression<Func<T, bool>>)null)
                throw new ArgumentNullException("filter", Messages.FilterCannotBeNullException);
            var set = this._dbSet.Where(filter);

            if (ascending)
            {
                if (includes.Any())
                {
                    return includes.Aggregate(set.OrderBy(orderByExpression).Skip(pageSize * pageIndex).Take(pageSize).AsQueryable(),
                                             (current, include) => current.Include(include));
                }
                return set.OrderBy(orderByExpression).Skip(pageSize * pageIndex).Take(pageSize);

            }
            else
            {
                if (includes.Any())
                {
                    return includes.Aggregate(set.OrderByDescending(orderByExpression).Skip(pageSize * pageIndex).Take(pageSize).AsQueryable(),
                                       (current, include) => current.Include(include));
                }
                return set.OrderByDescending(orderByExpression).Skip(pageSize * pageIndex).Take(pageSize);
            }
        }
    }
}
